/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package studio.raptor.sqlparser.dialect.oracle.parser;

import studio.raptor.sqlparser.ast.SQLExpr;
import studio.raptor.sqlparser.ast.SQLName;
import studio.raptor.sqlparser.ast.SQLPartition;
import studio.raptor.sqlparser.ast.SQLPartitionBy;
import studio.raptor.sqlparser.ast.SQLPartitionByHash;
import studio.raptor.sqlparser.ast.SQLPartitionByList;
import studio.raptor.sqlparser.ast.SQLPartitionByRange;
import studio.raptor.sqlparser.ast.SQLPartitionValue;
import studio.raptor.sqlparser.ast.SQLSubPartition;
import studio.raptor.sqlparser.ast.SQLSubPartitionBy;
import studio.raptor.sqlparser.ast.SQLSubPartitionByHash;
import studio.raptor.sqlparser.ast.SQLSubPartitionByList;
import studio.raptor.sqlparser.ast.expr.SQLIntegerExpr;
import studio.raptor.sqlparser.dialect.oracle.ast.clause.OracleLobStorageClause;
import studio.raptor.sqlparser.dialect.oracle.ast.clause.OracleStorageClause;
import studio.raptor.sqlparser.dialect.oracle.ast.stmt.OracleCreateTableStatement;
import studio.raptor.sqlparser.dialect.oracle.ast.stmt.OracleCreateTableStatement.DeferredSegmentCreation;
import studio.raptor.sqlparser.dialect.oracle.ast.stmt.OracleSelect;
import studio.raptor.sqlparser.parser.Lexer;
import studio.raptor.sqlparser.parser.ParserException;
import studio.raptor.sqlparser.parser.SQLCreateTableParser;
import studio.raptor.sqlparser.parser.Token;

public class OracleCreateTableParser extends SQLCreateTableParser {

  public OracleCreateTableParser(Lexer lexer) {
    super(new OracleExprParser(lexer));
  }

  public OracleCreateTableParser(String sql) {
    super(new OracleExprParser(sql));
  }

  protected OracleCreateTableStatement newCreateStatement() {
    return new OracleCreateTableStatement();
  }

  public OracleCreateTableStatement parseCreateTable(boolean acceptCreate) {
    OracleCreateTableStatement stmt = (OracleCreateTableStatement) super
        .parseCreateTable(acceptCreate);

    for (; ; ) {
      if (lexer.token() == Token.TABLESPACE) {
        lexer.nextToken();
        stmt.setTablespace(this.exprParser.name());
        continue;
      } else if (identifierEquals("IN_MEMORY_METADATA")) {
        lexer.nextToken();
        stmt.setInMemoryMetadata(true);
        continue;
      } else if (identifierEquals("CURSOR_SPECIFIC_SEGMENT")) {
        lexer.nextToken();
        stmt.setCursorSpecificSegment(true);
        continue;
      } else if (identifierEquals("NOPARALLEL")) {
        lexer.nextToken();
        stmt.setParallel(false);
        continue;
      } else if (lexer.token() == Token.LOGGING) {
        lexer.nextToken();
        stmt.setLogging(Boolean.TRUE);
        continue;
      } else if (lexer.token() == Token.CACHE) {
        lexer.nextToken();
        stmt.setCache(Boolean.TRUE);
        continue;
      } else if (lexer.token() == Token.NOCACHE) {
        lexer.nextToken();
        stmt.setCache(Boolean.FALSE);
        continue;
      } else if (lexer.token() == Token.NOCOMPRESS) {
        lexer.nextToken();
        stmt.setCompress(Boolean.FALSE);
        continue;
      } else if (lexer.token() == Token.ON) {
        lexer.nextToken();
        accept(Token.COMMIT);
        stmt.setOnCommit(true);
        continue;
      } else if (identifierEquals("PRESERVE")) {
        lexer.nextToken();
        acceptIdentifier("ROWS");
        stmt.setPreserveRows(true);
        continue;
      } else if (identifierEquals("STORAGE")) {
        OracleStorageClause storage = ((OracleExprParser) this.exprParser).parseStorage();
        stmt.setStorage(storage);
        continue;
      } else if (identifierEquals("organization")) {
        lexer.nextToken();
        accept(Token.INDEX);
        stmt.setOrganizationIndex(true);
        continue;
      } else if (lexer.token() == Token.PCTFREE) {
        lexer.nextToken();
        stmt.setPtcfree(this.exprParser.expr());
        continue;
      } else if (identifierEquals("PCTUSED")) {
        lexer.nextToken();
        stmt.setPctused(this.exprParser.expr());
        continue;
      } else if (lexer.token() == Token.STORAGE) {
        OracleStorageClause storage = ((OracleExprParser) this.exprParser).parseStorage();
        stmt.setStorage(storage);
        continue;
      } else if (lexer.token() == Token.LOB) {
        OracleLobStorageClause lobStorage = ((OracleExprParser) this.exprParser).parseLobStorage();
        stmt.setLobStorage(lobStorage);
        continue;
      } else if (lexer.token() == Token.INITRANS) {
        lexer.nextToken();
        stmt.setInitrans(this.exprParser.expr());
        continue;
      } else if (lexer.token() == Token.MAXTRANS) {
        lexer.nextToken();
        stmt.setMaxtrans(this.exprParser.expr());
        continue;
      } else if (lexer.token() == Token.SEGMENT) {
        lexer.nextToken();
        accept(Token.CREATION);
        if (lexer.token() == Token.IMMEDIATE) {
          lexer.nextToken();
          stmt.setDeferredSegmentCreation(DeferredSegmentCreation.IMMEDIATE);
        } else {
          accept(Token.DEFERRED);
          stmt.setDeferredSegmentCreation(DeferredSegmentCreation.DEFERRED);
        }
        continue;
      } else if (identifierEquals("PARTITION")) {
        lexer.nextToken();

        accept(Token.BY);

        if (identifierEquals("RANGE")) {
          SQLPartitionByRange partitionByRange = partitionByRange();
          partitionClauseRest(partitionByRange);
          stmt.setPartitioning(partitionByRange);
          continue;
        } else if (identifierEquals("HASH")) {
          SQLPartitionByHash partitionByHash = partitionByHash();
          partitionClauseRest(partitionByHash);
          stmt.setPartitioning(partitionByHash);
          continue;
        } else if (identifierEquals("LIST")) {
          SQLPartitionByList partitionByList = partitionByList();
          partitionClauseRest(partitionByList);
          stmt.setPartitioning(partitionByList);
          continue;
        } else {
          throw new ParserException("TODO : " + lexer.token() + " " + lexer.stringVal());
        }
      }
      break;
    }

    if (lexer.token() == Token.AS) {
      lexer.nextToken();

      OracleSelect select = new OracleSelectParser(exprParser).select();
      stmt.setSelect(select);
    }

    return stmt;
  }

  protected SQLPartitionByList partitionByList() {
    acceptIdentifier("LIST");
    SQLPartitionByList partitionByList = new SQLPartitionByList();

    accept(Token.LPAREN);
    partitionByList.setExpr(this.exprParser.expr());
    accept(Token.RPAREN);

    parsePartitionByRest(partitionByList);

    return partitionByList;
  }

  protected SQLPartitionByHash partitionByHash() {
    acceptIdentifier("HASH");
    SQLPartitionByHash partitionByHash = new SQLPartitionByHash();

    if (lexer.token() == Token.KEY) {
      lexer.nextToken();
      partitionByHash.setKey(true);
    }

    accept(Token.LPAREN);
    partitionByHash.setExpr(this.exprParser.expr());
    accept(Token.RPAREN);
    return partitionByHash;
  }

  protected SQLPartitionByRange partitionByRange() {
    acceptIdentifier("RANGE");
    accept(Token.LPAREN);
    SQLPartitionByRange clause = new SQLPartitionByRange();
    for (; ; ) {
      SQLName column = this.exprParser.name();
      clause.addColumn(column);

      if (lexer.token() == Token.COMMA) {
        lexer.nextToken();
        continue;
      }

      break;
    }
    accept(Token.RPAREN);

    if (lexer.token() == Token.INTERVAL) {
      lexer.nextToken();
      accept(Token.LPAREN);
      clause.setInterval(this.exprParser.expr());
      accept(Token.RPAREN);
    }

    parsePartitionByRest(clause);

    return clause;
  }

  protected void parsePartitionByRest(SQLPartitionBy clause) {
    if (lexer.token() == Token.STORE) {
      lexer.nextToken();
      accept(Token.IN);
      accept(Token.LPAREN);
      for (; ; ) {
        SQLName tablespace = this.exprParser.name();
        clause.getStoreIn().add(tablespace);

        if (lexer.token() == Token.COMMA) {
          lexer.nextToken();
          continue;
        }

        break;
      }
      accept(Token.RPAREN);
    }

    if (identifierEquals("SUBPARTITION")) {
      SQLSubPartitionBy subPartitionBy = subPartitionBy();
      clause.setSubPartitionBy(subPartitionBy);
    }

    accept(Token.LPAREN);

    for (; ; ) {
      SQLPartition partition = parsePartition();

      clause.addPartition(partition);

      if (lexer.token() == Token.COMMA) {
        lexer.nextToken();
        continue;
      }

      break;
    }

    accept(Token.RPAREN);
  }

  protected SQLPartition parsePartition() {
    acceptIdentifier("PARTITION");
    SQLPartition partition = new SQLPartition();
    partition.setName(this.exprParser.name());

    SQLPartitionValue values = this.exprParser.parsePartitionValues();
    if (values != null) {
      partition.setValues(values);
    }

    if (lexer.token() == Token.LPAREN) {
      lexer.nextToken();

      for (; ; ) {
        SQLSubPartition subPartition = parseSubPartition();

        partition.addSubPartition(subPartition);

        if (lexer.token() == Token.COMMA) {
          lexer.nextToken();
          continue;
        }

        break;
      }

      accept(Token.RPAREN);
    } else if (identifierEquals("SUBPARTITIONS")) {
      lexer.nextToken();
      SQLExpr subPartitionsCount = this.exprParser.primary();
      partition.setSubPartitionsCount(subPartitionsCount);
    }
    return partition;
  }

  protected SQLSubPartition parseSubPartition() {
    acceptIdentifier("SUBPARTITION");

    SQLSubPartition subPartition = new SQLSubPartition();
    SQLName name = this.exprParser.name();
    subPartition.setName(name);

    SQLPartitionValue values = this.exprParser.parsePartitionValues();
    if (values != null) {
      subPartition.setValues(values);
    }

    return subPartition;
  }

  protected void partitionClauseRest(SQLPartitionBy clause) {
    if (identifierEquals("PARTITIONS")) {
      lexer.nextToken();

      SQLIntegerExpr countExpr = this.exprParser.integerExpr();
      clause.setPartitionsCount(countExpr);
    }

    if (lexer.token() == Token.STORE) {
      lexer.nextToken();
      accept(Token.IN);
      accept(Token.LPAREN);
      this.exprParser.names(clause.getStoreIn(), clause);
      accept(Token.RPAREN);
    }
  }

  protected SQLSubPartitionBy subPartitionBy() {
    lexer.nextToken();
    accept(Token.BY);

    if (identifierEquals("HASH")) {
      lexer.nextToken();
      accept(Token.LPAREN);

      SQLSubPartitionByHash byHash = new SQLSubPartitionByHash();
      SQLExpr expr = this.exprParser.expr();
      byHash.setExpr(expr);
      accept(Token.RPAREN);

      return byHash;
    } else if (identifierEquals("LIST")) {
      lexer.nextToken();
      accept(Token.LPAREN);

      SQLSubPartitionByList byList = new SQLSubPartitionByList();
      SQLName column = this.exprParser.name();
      byList.setColumn(column);
      accept(Token.RPAREN);

      if (identifierEquals("SUBPARTITION")) {
        lexer.nextToken();
        acceptIdentifier("TEMPLATE");
        accept(Token.LPAREN);

        for (; ; ) {
          SQLSubPartition subPartition = parseSubPartition();
          subPartition.setParent(byList);
          byList.getSubPartitionTemplate().add(subPartition);

          if (lexer.token() == Token.COMMA) {
            lexer.nextToken();
            continue;
          }
          break;
        }
        accept(Token.RPAREN);
      }

      return byList;
    }

    throw new ParserException("TODO : " + lexer.token() + " " + lexer.stringVal());
  }

}
